/*
 * @Description: 我们的目标是用简单的代码搞些高级的玩意儿
 * @Autor: lele
 * @Date: 2021-11-17 16:24:28
 * @FilePath: \zz-1111\src\components\js\state\corresponing.js
 * @LastEditTime: 2021-11-23 20:52:27
 * @note: 现阶段实现的响应式仓库状态还需要进一步改进，比如类型检查，入参校验等等,不过有一件好消息，proxy支持数组，嵌套数组，对象，嵌套对象的监听！麻麻再也不用担心监听不到了😂
 * @demo: https://gitee.com/SimonaGo/corresponding-formula
 */
const watcherFile = require("./watch.js");
const stateFile = require("./store/state.js");
const actionFile = require("./store/action.js");
const lodash = require('lodash');

let watcher = watcherFile.watcher;
let state = stateFile.state;
let action = actionFile.action;

class Observer {
	
	constructor(obj) {
		this._orData = null; // 被监听的仓库原始状态
		this.state = obj.state; // 被监听的仓库状态
		this.action = obj.action; // 被注册的仓库事件
		this.watcher = obj.watcher; // 观察者
		this.queueActions = new Set(); // 仓库事件队列
		this.obData = Object.create(null); // 代理的仓库状态
		this.reset = null; // 记录观察者
		this.deepClone(obj.state); // 保存原始状态值，这里用到了深拷贝
		this.cacheWatcher(obj.state); // 初始化 观察者
		this.addObserve(); // 初始化仓库事件队列
		this.observable(); // 激活仓库状态代理 
		this.protectValue(['_orData']); // 保护以下划线开头的属性，只读
	}
	observable() {
		let that = this
		this.obData = new Proxy(that.state, {
			set: function (target, propKey, value, receiver) {
				if (that.reset) {
					that.reset.colletionObserve({
						"target": target,
						"key": propKey,
						"val": value
					});
				} else {
					that.reset = new that.watcher({
						"target": target,
						"key": key,
						"val": val
					}); // 不出意外的话，这一步是执行不到的
				}
				return Reflect.set(target, propKey, value);
			} // state 数据发生变动通知观察者
		});
	}
	addObserve() {
		this.queueActions.add(Reflect.ownKeys(this.action));
	}
	commit(key, val) { // 可以在提交事件的时候传入参数
		let that = this;
		this.queueActions.forEach(observer => {
			for (let item of observer) {
				if (item == key) {
					that.action[key](that.obData, val);
				}
			}
		});
	}
	cacheWatcher(obj) {
		this.reset = new this.watcher(obj, this._orData); // 注册观察者watcher
	}
	deepClone(target) {
		this._orData = lodash.cloneDeep(target); // 万能的lodash
	}
	protectValue(targetObj) {
		const handler = {
			get(target, key) {
				return target[key];
			},
			set(target, key, value) {
				console.error(`you can't change ${key} ,cause it's a private property`)
				invariant(key, 'set');
				return true;
			}
		};
		function invariant(key, action) {
			try {
				throw new Error(`Invalid attempt to ${action} private "${key}" property`);
			} catch (e) {
				console.log(e)
			}
		}
		for (let item of targetObj) { // 对带有_开头的对象实现了私有保护，只读
			if (item[0] === '_') {
				this[item] = new Proxy(this[item], handler)
			}
		}
	}
	
}

let store = new Observer({
	"state": state,
	"action": action,
	"watcher": watcher
});
let watchObserve = store.reset;
exports.store = store;
exports.watcherData = watchObserve;

//1.0.1

/* 
em , 现在对于所有的微件，store和watcherData都是可见的，而且仓库的变动和观察都是同步的。比如你在某一个微件中这样写：
store.commit('age'); 此时仓库中会触发action事件（age），改变某一个状态。
let age = store.state.age // 现在立马就可以拿到改变后的值，即使不在同一个微件中。但还不是响应式的监听，响应式的监听需要通过下面的方法。
如果你在另外一个微件中 刚好监听到了 age ,而且此时的微件处于激活状态，那么就会收到通知（新值），像这样：
let watch = {
	age:function(newvalue){
	  console.log('法外狂徒:',store.state.names,'他来了,今年他',newvalue,'岁')
	}
};
watcherData.watch({"methods":watch})
虽然上面监听的写法好像不是那么优雅，不过对于zz3d.widget.bindClass(zz3d.widget.BaseWidget.extend({})) , 看起来还可以接受。 
*/


// 1.0.2 

/* 
新的watch，现在添加了对于未定义数据和错误的书写方式的处理，当你在页面监听store中的状态时，请留意控制台的错误提示，
以console.error()的形式提示你，该监听状态没有在state中声明或者类型错误.
并实现了对于特定数据的私有保护
除此之外，在初始化观察的时候，会执行watch中方法，而且会返回state中的初始化状态，该初始化状态不会随着其他操作的改变而改变。
现在,可以这样写响应式了：
let watch = {
	age:function(orData,newvalue){
	  if(orData == newvalue){
         console.log('age 值未被改变') 
      }else{
		console.log('age 值改变了，该值是newvalue')
	  }
	  
	}
}; 
 watcherData.watch({"methods":watch})   也就是说现在是主动的监听状态，在页面激活的时候会执行watch中的方法 
*/


// 1.1.0 

/* 可喜可贺，本来是想引入一个diff,计算多次相同监听情况下，用于清除缓存的，才发现，每个微件对于state来说是不可见的，state对于微件的卸载一脸懵逼😂。所以diff的引入失败了。但diff只是被注释了，或许以后有大用。

but !!! 现在定义了一个clearWatchMemory(),用以清除内存中的垃圾。需要手动在微件销毁的时候触发，像这样：

 disable: function () {
        watcherData.clearWatchMemory()
    },

这个方法里面也对调用时机做了处理，如果在没有监听的时候调用此方法会向外抛出错误，额。。，其实是个错误打印提示。不影响程序的后续执行。

如果大家在开发的时候用到了本仓库，请在微件销毁的时候手动触发一下clearWatchMemory()方法，额。。，其实也不是非得触发不可。
但是触发后可以减少频繁触发监听状态下内存的溢出风险。其实也没那么严重，一个数组导致的内存溢出，除非是在页面中写了个死循环😂

所以 这个改动直接到 1.1.0 了。😘
*/


// 1.1.1 (stable)

/* 对watch 做了一些升级:
  1、对变动数据做了一层代理，如果重复commit同一个值，现在不会多次触发相关监听事件
  2、首次触发监听事件，添加了对于当前页面的判断，不会触发所有的监听

  在多次测试下，现在的watch 可以满足基本需求。
 */